package cn.edu.neu.lab603.cloudeval.entity

import java.util.concurrent.atomic.AtomicLong

import cn.edu.neu.lab603.cloudeval.CloudEval
import cn.edu.neu.lab603.cloudeval.entity.VirtualMachine.{BootEvent, MigrationPreCopyEvent, NetworkPacket, ShutdownEvent}
import cn.edu.neu.lab603.des.{SimEntity, SimEvent}
import com.typesafe.scalalogging.LazyLogging

import scala.collection.immutable.Queue
import scala.collection.mutable.ListBuffer
import scala.collection.{mutable, Map => AbsMap}

/** 虚拟机实体。
  *
  * @param pm     物理机
  * @param resMap 资源规格
  */
class VirtualMachine(private var pm: PhysicalMachine,
                     private val resMap: scala.collection.Map[String, Int]) extends SimEntity with LazyLogging {

  // 隐含指针
  private implicit val self = this

  /** 虚拟机的唯一标识。 */
  val vmNo: Long = VirtualMachine.idGen

  // 虚拟机利用率的历史记录
  private val utilHistory = new mutable.HashMap[String, ListBuffer[Double]]
  // 请求利用率的缓存
  private var reqResMap: scala.collection.Map[String, Int] = _

  def this(physicalMachine: PhysicalMachine, cloudlet: CloudTask) = {
    this(physicalMachine, cloudlet.getResSpecMap)
    this.cloudlet = cloudlet
  }

  /** 获取宿主物理机。 */
  def host: PhysicalMachine = pm

  /** 设置当前的目标物理服务器。 */
  private[entity] def host_(target: PhysicalMachine) = pm = target

  /** 设置宿主物理机。 */
  private[entity] def host_=(pm: PhysicalMachine) = this.pm = pm

  private var cloudlet: CloudTask = _
  private var status = VirtualMachine.Status.OFF

  /** 检查资源的消耗情况。
    *
    * @param cloudletResMap 云计算任务
    * @return 布尔值
    */
  def fit(cloudletResMap: scala.collection.Map[String, Int]): Boolean = {
    // 检查资源消耗
    for ((key, value) <- resMap) {
      if (cloudletResMap(key) > value) {
        return false
      }
    }
    true
  }

  def setCloudlet(cloudlet: CloudTask): Unit = this.cloudlet = cloudlet

  def getCloudlet: CloudTask = cloudlet

  private[entity] def setStatus(status: VirtualMachine.Status) = this.status = status

  /** 获得当前虚拟机状态。
    *
    * @return 状态值
    */
  def getStatus: _root_.cn.edu.neu.lab603.cloudeval.entity.VirtualMachine.Status.Value = this.status

  /** 获取虚拟机规格。
    *
    * @return 虚拟机规格映射
    */
  def getSpecResMap: scala.collection.Map[String, Int] = resMap

  /** 获取资源使用情况。
    *
    * @return 资源使用模型
    */
  def getUsedResMap: scala.collection.Map[String, Int] = {
    if (cloudlet != null)
      cloudlet.resMap.mapValues(_.getValue)
    else
      resMap.mapValues(_ => 0)
  }

  /** 获取当前利用率的百分比
    *
    * @return 资源利用率百分比
    */
  def getUsedResPct: scala.collection.Map[String, Double] = getUsedResMap.map(p => (p._1, p._2.toDouble / resMap(p._1)))

  /** 获取请求的资源使用情况。
    *
    * @return 资源使用模型
    */
  def getReqResMap: scala.collection.Map[String, Int] = {
    if (cloudlet != null && cloudlet.getStatus == CloudTask.Status.RUNNING && reqResMap != null)
      reqResMap
    else
      resMap.mapValues(_ => 0)
  }

  /** 网络模块。 */
  object net {

    private[entity] val packetQueue = new mutable.Queue[VirtualMachine.NetworkPacket]()

    def send(cloudlet: CloudTask, size: Int): Unit = send(cloudlet.getExecutionContext, size)

    def send(cloudlet: CloudTask, size: Int, content: String): Unit = send(cloudlet.getExecutionContext, size, content)

    def send(vm: VirtualMachine, size: Int): Unit = send(vm.id, size)

    def send(vm: VirtualMachine, size: Int, content: String): Unit = send(vm.id, size, content)

    /** 发送网络包到虚拟机集群中。
      *
      * @param id      另一个虚拟机的ID
      * @param size    大小
      * @param content 内容描述（可选）
      */
    def send(id: Long, size: Int, content: String = null): Unit = {
      if (resMap contains "bw") {
        if (resMap("bw") > size) {
          val packet = new VirtualMachine.NetworkPacket(VirtualMachine.this.id, id, size, 0, size, content)
          packetQueue enqueue packet
        } else {
          var len = size
          var idx = 0
          val bw = resMap("bw")
          while (len > 0) {
            val packet = new VirtualMachine.NetworkPacket(VirtualMachine.this.id, id, size, idx, math.min(len, bw), content)
            packetQueue enqueue packet
            len -= bw
            idx += bw
          }
        }
      } else {
        logger.trace(s"network is unsupported in VM#$vmNo")
      }
    }

    private[entity] def received(data: VirtualMachine.NetworkPacket): Unit = {
      if (cloudlet != null) {
        cloudlet.receivedNetPacket(data)
      }
    }

    def queue: Queue[NetworkPacket] = packetQueue.to[Queue]
  }

  /** 启动虚拟机。 */
  def boot(): Unit = {
    if (status == VirtualMachine.Status.OFF) {
      setStatus(VirtualMachine.Status.BOOT)

      context.submitTask(
        VirtualMachine.BOOT_DELAY,
        new BootEvent
      )
    } else if (status == VirtualMachine.Status.SHUTDOWN) {
      setStatus(VirtualMachine.Status.ON)
    }
  }

  /** 更新虚拟机的资源分配 */
  def update(clock: Int, remainResMap: AbsMap[String, Int]): AbsMap[String, Int] = {
    if (getReqResMap.forall(r => r._2 <= remainResMap.getOrElse(r._1, 0))) {
      fullyUpdate(clock)
    } else if (getReqResMap.exists(_._2 >= 0)) {
      partialUpdate(clock, remainResMap)
    } else Map.empty
  }

  /** 更新资源分配（所有资源都满足） **/
  protected def fullyUpdate(clock: Int): AbsMap[String, Int] = {
    if (cloudlet != null) {
      cloudlet.update(clock)

      getUsedResPct.foreach { p =>
        utilHistory.getOrElseUpdate(p._1, mutable.ListBuffer.empty[Double]) += p._2
      }

      val usedResMap = getReqResMap
      reqResMap = cloudlet.resMap.mapValues(_.getNext)

      usedResMap
    } else Map.empty
  }

  /** 更新资源分配（部分） */
  protected def partialUpdate(clock: Int, remainResMap: AbsMap[String, Int]): AbsMap[String, Int] = {
    if (cloudlet != null) {
      val used = mutable.HashMap.empty[String, Int] ++ remainResMap
      reqResMap = reqResMap.map { case (res: String, req: Int) =>
        val remain = remainResMap.getOrElse(res, 0)
        if (remain >= req) {
          used(res) = req
          res -> 0
        } else {
          used(res) = remain
          res -> (req - remain)
        }
      }
      getUsedResPct.foreach { p =>
        utilHistory.getOrElseUpdate(p._1, mutable.ListBuffer.empty[Double]) += 1
      }
      used
    } else Map.empty
  }

  /** 关闭虚拟机。 */
  def shutdown(): Unit = {
    if (status == VirtualMachine.Status.ON) {
      setStatus(VirtualMachine.Status.SHUTDOWN)

      context.submitTask(
        VirtualMachine.SHUTDOWN_DELAY,
        new ShutdownEvent
      )
    } else if (status == VirtualMachine.Status.BOOT) {
      setStatus(VirtualMachine.Status.OFF)
    }
  }

  /** 直接销毁虚拟机。 */
  def destroy(): Unit = {
    if (status != VirtualMachine.Status.OFF) {
      setStatus(VirtualMachine.Status.OFF)
      context.submitTaskOnNextTick(new ShutdownEvent)
    }
  }

  /** 迁移该虚拟机到指定的物理机上。
    *
    * @param pm 物理机
    */
  def migrate(pm: PhysicalMachine): Unit = {
    if (pm.getStatus == PhysicalMachine.Status.ON) {
      if (getStatus != VirtualMachine.Status.MIGRATING) {
        context.submitTask(0, new MigrationPreCopyEvent(this, pm))
        setStatus(VirtualMachine.Status.MIGRATING)
      }
    }
  }

  /** 测试虚拟机是否空闲。
    *
    * @return 布尔值
    */
  def isIdle: Boolean = getCloudlet == null || getCloudlet.getStatus == CloudTask.Status.STOPPED

  /** 该虚拟机是否可以运行。
    *
    * @return 布尔值
    */
  def isRunning: Boolean = status == VirtualMachine.Status.ON || status == VirtualMachine.Status.MIGRATING

  /** 获取虚拟机的使用记录。
    *
    * @return
    */
  def getUtilizationHistory: scala.collection.Map[String, List[Double]] = utilHistory.mapValues(_.result)

  private def context = pm.context

  override def toString = s"VM#$vmNo"
}

object VirtualMachine {
  val BOOT_DELAY = 5
  val SHUTDOWN_DELAY = 5
  val MIGRATION_DELAY = 5

  private val idCnt = new AtomicLong()
  private val cache = new mutable.HashMap[Long, VirtualMachine]

  private def idGen(implicit vm: VirtualMachine): Long = {
    val _id = idCnt.getAndIncrement()
    cache(_id) = vm
    _id
  }

  def get(vmNo: Long): Option[VirtualMachine] = cache.get(vmNo)

  /** 虚拟机状态。 */
  object Status extends Enumeration {
    val ON, BOOT, MIGRATING, SHUTDOWN, OFF = Value
  }

  type Status = Status.Value

  /** 虚拟机启动任务。
    *
    * @param vm 虚拟机
    */
  class BootEvent(implicit val vm: VirtualMachine) extends SimEvent[CloudEval] with LazyLogging {
    override def execute(context: CloudEval): Unit = {
      if (vm.getStatus == VirtualMachine.Status.BOOT) {
        vm.setStatus(VirtualMachine.Status.ON)

        logger.debug(s"[${context.getCurrentClock}] $vm is turned on.")
      }
    }

    override def effective: Boolean = vm.getStatus == Status.ON || vm.getStatus == Status.BOOT
  }

  class MigrationPreCopyEvent(val vm: VirtualMachine, val targetHost: PhysicalMachine) extends SimEvent[CloudEval] with LazyLogging {
    override def execute(context: CloudEval): Unit = {
      // 从目标主机中复制该虚拟机（预复制）。
      implicit val newVM = targetHost.preCopyVirtualMachine(vm)

      // 实际迁移该虚拟机（停机复制）。
      context.submitTask(
        VirtualMachine.MIGRATION_DELAY,
        new MigrationPostCopyEvent(newVM, targetHost)
      )

      logger.debug(s"[${context.getCurrentClock}] $vm from ${vm.host} started pre-copy to $targetHost.")
    }
  }

  class MigrationPostCopyEvent(val vm: VirtualMachine, val targetHost: PhysicalMachine) extends SimEvent[CloudEval] with LazyLogging {

    override def execute(context: CloudEval): Unit = {
      implicit val target = targetHost
      vm.destroy()

      target.postCopyVirtualMachine(vm)

      logger.debug(s"[${context.getCurrentClock}] $vm started post-copy to $target.")
    }

  }

  /** 虚拟机停止任务。
    *
    * @param vm 虚拟机
    */
  class ShutdownEvent(implicit val vm: VirtualMachine) extends SimEvent[CloudEval] with LazyLogging {
    override def execute(context: CloudEval): Unit = {
      if (vm.getStatus == VirtualMachine.Status.SHUTDOWN) {
        vm.setStatus(VirtualMachine.Status.OFF)

        logger.debug(s"[${context.getCurrentClock}] $vm is stopped.")
      }
    }

    override def effective: Boolean = vm.getStatus == Status.OFF || vm.getStatus == Status.SHUTDOWN
  }

  class NetworkPacket(val from: Long,
                      val to: Long,
                      val total: Int,
                      val idx: Int,
                      val length: Int,
                      val content: String = null)(implicit val vm: VirtualMachine) {
    override def toString: String = s"Packet[from=$from,to=$to,total=$total]"
  }

}